
// @ts-nocheck
import jsSHA from "jssha"
import { WordArray } from './lib-WordArray';
import { X64Word } from './x64_core';
import { BufferedBlockAlgorithmConfig } from './BufferedBlockAlgorithmConfig';
import { Hasher } from './AES/lib/Hasher';

var RHO_OFFSETS = [];
var PI_INDEXES = [];
var ROUND_CONSTANTS = [];


// Compute Constants
(function(){
  // Compute rho offset constants
  var x = 1, y = 0;
  for (var t = 0; t < 24; t++) {
    RHO_OFFSETS[x + 5 * y] = ((t + 1) * (t + 2) / 2) % 64;

    var newX = y % 5;
    var newY = (2 * x + 3 * y) % 5;
    x = newX;
    y = newY;
  }
  // Compute pi index constants
  for (var x = 0; x < 5; x++) {
    for (var y = 0; y < 5; y++) {
      PI_INDEXES[x + 5 * y] = y + ((2 * x + 3 * y) % 5) * 5;
    }
  }

  // Compute round constants
  var LFSR = 0x01;
  for (var i = 0; i < 24; i++) {
    let roundConstantMsw: number = 0;
    let roundConstantLsw: number = 0;

    for (var j = 0; j < 7; j++) {
      if (LFSR & 0x01) {
        var bitPosition = (1 << j) - 1;
        if (bitPosition < 32) {
          roundConstantLsw ^= 1 << bitPosition;
        } else /* if (bitPosition >= 32) */
        {
          roundConstantMsw ^= 1 << (bitPosition - 32);
        }
      }

      // Compute next LFSR
      if (LFSR & 0x80) {
        // Primitive polynomial over GF(2): x^8 + x^6 + x^5 + x^4 + 1
        LFSR = (LFSR << 1) ^ 0x71;
      } else {
        LFSR <<= 1;
      }
    }
    if (roundConstantLsw == 0)roundConstantLsw = 1
    ROUND_CONSTANTS[i] = new X64Word(roundConstantMsw, roundConstantLsw);
  }
}());

var T = [];
var _state = [];
(function(){
  for (var i = 0; i < 25; i++) {
    T[i] = new X64Word();
  }
}());

export class sha3 extends Hasher {
  /**
   *
   * @param s
   * @param outputLength 默认512，SHA3-256, SHA3-384, SHA3-512
   */
  hex_sha3(s, outputLength?: number) {
    let localsha3 = Hasher._createHelper(sha3);
    return localsha3.helper(s, { outputLength: outputLength })
  }

  hex_hmac_sha3(k, d, outputLength?: number) {
    var shaObj
    if (outputLength != null) {
      var le = "SHA3-" + outputLength
      shaObj = new jsSHA(le, "TEXT", {
        hmacKey: { value: d, format: "TEXT", encoding: "UTF8" },
      });
    } else {
      shaObj = new jsSHA("SHA3-512", "TEXT", {
        hmacKey: { value: d, format: "TEXT", encoding: "UTF8" },
      });
    }

    shaObj.update(k);
    const hmac = shaObj.getHash("HEX");
    return hmac;
  }

  public constructor(xformMode: number, key: WordArray, cfg?: BufferedBlockAlgorithmConfig) {
    super(xformMode, key, Object.assign({ blockSize: 512 / 32 }, cfg)) //512/32

    try {
      this.cfg.outputLength = cfg.outputLength
    } catch (e) {
      Object.assign({ outputLength: 512 }, cfg)
      this.cfg.outputLength = 512
    }
    this._doReset()
  }

  _doFinalize() {
    // Shortcuts
    var data = this._data;
    var dataWords = data.words;
    var nBitsTotal = this._nDataBytes * 8;
    var nBitsLeft = data.sigBytes * 8;
    var blockSizeBits = this.cfg.blockSize * 32;
    // Add padding
    dataWords[nBitsLeft >>> 5] |= 0x1 << (24 - nBitsLeft % 32);
    dataWords[((Math.ceil((nBitsLeft + 1) / blockSizeBits) * blockSizeBits) >>> 5) - 1] |= 0x80;
    data.sigBytes = dataWords.length * 4;
    // Hash final blocks

    this._process();

    // Shortcuts
    var state = _state;
    var outputLengthBytes = this.cfg.outputLength / 8;
    var outputLengthLanes = outputLengthBytes / 8;
    // Squeeze
    var hashWords = [];
    for (var i = 0; i < outputLengthLanes; i++) {
      // Shortcuts
      var lane = state[i];
      var laneMsw = lane.high
      var laneLsw = lane.low

      // Swap endian
      laneMsw = (
        (((laneMsw << 8) | (laneMsw >>> 24)) & 0x00ff00ff) |
        (((laneMsw << 24) | (laneMsw >>> 8)) & 0xff00ff00)
      );
      laneLsw = (
        (((laneLsw << 8) | (laneLsw >>> 24)) & 0x00ff00ff) |
        (((laneLsw << 24) | (laneLsw >>> 8)) & 0xff00ff00)
      );

      // Squeeze state to retrieve hash
      hashWords.push(laneLsw);
      hashWords.push(laneMsw);
    }

    // Return final computed hash
    return new WordArray(hashWords, outputLengthBytes);
  }

  _doReset() {
    var state = _state = []
    for (var i = 0; i < 25; i++) {
      state[i] = new X64Word();
    }

    this.cfg.blockSize = (1600 - 2 * this.cfg.outputLength) / 32;
  }

  _doProcessBlock(M, offset) {
    // Shortcuts
    var state = _state;
    var nBlockSizeLanes = this.cfg.blockSize / 2;
    // Absorb
    for (var i = 0; i < nBlockSizeLanes; i++) {
      // Shortcuts
      var M2i = M[offset + 2 * i];
      var M2i1 = M[offset + 2 * i + 1];

      // Swap endian
      M2i = (
        (((M2i << 8) | (M2i >>> 24)) & 0x00ff00ff) |
        (((M2i << 24) | (M2i >>> 8)) & 0xff00ff00)
      );

      M2i1 = (
        (((M2i1 << 8) | (M2i1 >>> 24)) & 0x00ff00ff) |
        (((M2i1 << 24) | (M2i1 >>> 8)) & 0xff00ff00)
      );

      // Absorb message into state
      var lane = state[i];
      lane.high ^= M2i1;
      lane.low ^= M2i;
    }

    // Rounds
    for (var round = 0; round < 24; round++) {
      // Theta
      for (let x = 0; x < 5; x++) {
        // Mix column lanes
        var tMsw = 0, tLsw = 0;
        for (var y = 0; y < 5; y++) {
          let lane = state[x + 5 * y];
          tMsw ^= lane.high;
          tLsw ^= lane.low;
        }

        // Temporary values
        var Tx = T[x];
        Tx.high = tMsw;
        Tx.low = tLsw;
      }
      for (let x = 0; x < 5; x++) {
        // Shortcuts
        var Tx4 = T[(x + 4) % 5];
        var Tx1 = T[(x + 1) % 5];
        var Tx1Msw = Tx1.high;
        var Tx1Lsw = Tx1.low;

        // Mix surrounding columns
        let tMsw = Tx4.high ^ ((Tx1Msw << 1) | (Tx1Lsw >>> 31));
        let tLsw = Tx4.low ^ ((Tx1Lsw << 1) | (Tx1Msw >>> 31));
        for (let y = 0; y < 5; y++) {
          let lane = state[x + 5 * y];
          lane.high ^= tMsw;
          lane.low ^= tLsw;
        }
      }

      // Rho Pi
      for (var laneIndex = 1; laneIndex < 25; laneIndex++) {
        let tMsw;
        let tLsw;
        // Shortcuts
        let lane = state[laneIndex];
        var laneMsw = lane.high;
        var laneLsw = lane.low;
        var rhoOffset = RHO_OFFSETS[laneIndex];

        // Rotate lanes
        if (rhoOffset < 32) {
          tMsw = (laneMsw << rhoOffset) | (laneLsw >>> (32 - rhoOffset));
          tLsw = (laneLsw << rhoOffset) | (laneMsw >>> (32 - rhoOffset));
        } else /* if (rhoOffset >= 32) */
        {
          tMsw = (laneLsw << (rhoOffset - 32)) | (laneMsw >>> (64 - rhoOffset));
          tLsw = (laneMsw << (rhoOffset - 32)) | (laneLsw >>> (64 - rhoOffset));
        }

        // Transpose lanes
        var TPiLane = T[PI_INDEXES[laneIndex]];

        TPiLane.high = tMsw;
        TPiLane.low = tLsw;
      }

      // Rho pi at x = y = 0
      var T0 = T[0];
      var state0 = state[0];
      T0.high = state0.high;
      T0.low = state0.low;

      // Chi
      for (let x = 0; x < 5; x++) {
        for (let y = 0; y < 5; y++) {
          // Shortcuts
          let laneIndex = x + 5 * y;
          let lane = state[laneIndex];
          let TLane = T[laneIndex];
          let Tx1Lane = T[((x + 1) % 5) + 5 * y];
          let Tx2Lane = T[((x + 2) % 5) + 5 * y];

          // Mix rows
          lane.high = TLane.high ^ (~Tx1Lane.high & Tx2Lane.high);
          lane.low = TLane.low ^ (~Tx1Lane.low & Tx2Lane.low);

        }
      }

      // Iota
      let lane = state[0];
      var roundConstant = ROUND_CONSTANTS[round];
      lane.high ^= roundConstant.high;
      lane.low ^= roundConstant.low;

    }
  }

}